CtrlK
BlogDocsLog inGet started
Tessl Logo

npx Package Development

This skill should be used when the user asks to "create an npx package", "build a CLI tool with TypeScript", "set up a Node.js CLI", "publish an npm package", "configure package.json bin field", "add CLI argument parsing", "create an executable npm package", or when writing TypeScript code for a command-line tool distributed via npx. Covers package structure, TypeScript configuration, argument parsing, build pipeline, and npm publishing.

Invalid
This skill can't be scored yet
Validation errors are blocking scoring. Review and fix them to unlock Quality, Impact and Security scores. See what needs fixing →
SKILL.md
Quality
Evals
Security

npx Package Development (TypeScript)

This skill covers building CLI tools in TypeScript that are executable via npx. An npx package is a standard npm package with a bin entry point — npx downloads and runs it without global installation.

Package Structure

my-cli/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts          # entry point (bin target)
│   ├── cli.ts            # argument parsing and command routing
│   └── commands/         # command implementations
│       └── install.ts
├── dist/                 # compiled output (gitignored)
└── README.md

package.json Essentials

{
  "name": "my-cli",
  "version": "1.0.0",
  "description": "CLI tool description",
  "type": "module",
  "bin": {
    "my-cli": "./dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "prepublishOnly": "npm run build"
  },
  "engines": {
    "node": ">=18"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "@types/node": "^20.0.0"
  }
}

Key fields:

FieldPurpose
namePackage name on npm. Use @scope/name for scoped packages
binMaps command names to compiled JS entry points
filesWhitelist of files included in the published package
type: "module"Enable ESM (import/export syntax)
enginesMinimum Node.js version requirement
prepublishOnlyAuto-builds before npm publish

Entry Point with Shebang

The entry point file must start with a Node.js shebang:

#!/usr/bin/env node

// src/index.ts
import { run } from "./cli.js";

run(process.argv.slice(2));

The shebang (#!/usr/bin/env node) tells the OS to execute the file with Node.js. Without it, npx execution fails on Unix systems.

Important: The shebang must be the very first line — no blank lines or comments above it.

TypeScript Configuration

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Key settings:

  • module: "Node16" + moduleResolution: "Node16" — required for proper ESM support in Node.js.
  • outDir: "./dist" — compiled JS goes to dist/, matching the bin path in package.json.
  • declaration: true — emit .d.ts files for consumers who import the package programmatically.

ESM import rule: When using type: "module", all relative imports must include the .js extension, even in .ts source files:

// Correct
import { run } from "./cli.js";

// Wrong — will fail at runtime
import { run } from "./cli";

Argument Parsing

Manual parsing (zero dependencies)

For simple CLIs with 1-3 commands:

// src/cli.ts
export function run(args: string[]): void {
  const command = args[0];

  switch (command) {
    case "install":
      handleInstall(args.slice(1));
      break;
    case "search":
      handleSearch(args.slice(1));
      break;
    case "--help":
    case "-h":
      printHelp();
      break;
    case "--version":
    case "-v":
      printVersion();
      break;
    default:
      console.error(`Unknown command: ${command}`);
      printHelp();
      process.exit(1);
  }
}

Flag extraction helper

function getFlag(args: string[], flag: string): string | undefined {
  const index = args.indexOf(flag);
  if (index === -1 || index + 1 >= args.length) return undefined;
  return args[index + 1];
}

// Usage: npx my-cli install --name my-plugin
const name = getFlag(args, "--name");

With a parsing library (for complex CLIs)

For CLIs with many commands, flags, and subcommands, use a library like commander or yargs. See references/patterns.md for detailed examples.

Output and Exit Codes

Colored output (zero dependencies)

Node.js supports ANSI escape codes natively:

const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;

console.log(green("✓ Installed successfully"));
console.error(red("✗ Installation failed"));

Exit codes

process.exit(0);  // success
process.exit(1);  // general error
process.exit(2);  // misuse (bad arguments)

Always call process.exit() with an appropriate code on failure. A non-zero exit code signals failure to the calling process.

Stderr vs stdout

  • console.log() → stdout — for program output (data, results).
  • console.error() → stderr — for diagnostics (errors, warnings, progress).

Build and Test Locally

# Build
npm run build

# Test locally (symlinks the package globally)
npm link
my-cli --help

# Or test with npx directly
npx . install --name my-plugin

# Unlink when done
npm unlink -g my-cli

Publishing to npm

# Login (one-time)
npm login

# Publish (runs prepublishOnly → build automatically)
npm publish

# Publish scoped package as public
npm publish --access public

After publishing, the package is immediately available via:

npx my-cli install --name my-plugin

Versioning

npm version patch   # 1.0.0 → 1.0.1
npm version minor   # 1.0.0 → 1.1.0
npm version major   # 1.0.0 → 2.0.0

Run npm version before npm publish — it updates package.json and creates a git tag.

Common Mistakes

MistakeFix
Missing shebangAdd #!/usr/bin/env node as first line of entry point
Shebang not on first lineRemove blank lines/comments above #!/usr/bin/env node
Missing .js in ESM importsAdd .js extension to all relative imports
bin points to .ts filePoint to compiled .js in dist/
dist/ not in filesAdd "files": ["dist"] to package.json
No prepublishOnly scriptAdd "prepublishOnly": "npm run build"
File not executable on UnixRun chmod +x dist/index.js (usually handled by npm)

Additional Resources

Reference Files

For detailed patterns and advanced configuration, consult:

  • references/patterns.md — Commander/yargs argument parsing, interactive prompts, spinner/progress bars, HTTP requests from CLI, error handling patterns, monorepo setup
  • references/package-setup.md — Advanced package.json fields, scoped packages, bundling with esbuild/tsup, dual CJS/ESM support, CI/CD publishing, npm provenance
Repository
provectus/awos-recruitment
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.